/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 combat_user_action.cpp

	$Header: /heroes4/combat_user_action.cpp $

	$NoKeywords: $

 ************************************************************************/
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "combat_user_action.h"

#include "battlefield.h"
#include "combat_creature.h"
#include "combat_spell.h"
#include "combat_window.h"
#include "external_string.h"
#include "replace_keywords.h"

namespace
{
	// --------------------------------------------------------
	// area effect ranged attack
	// --------------------------------------------------------
	class t_combat_user_action_area : public t_combat_user_action
	{
	public:
		t_combat_user_action_area( t_screen_point const&	screen_point,
								   t_map_point_2d const&	target_point,
								   t_combat_creature*		target );

		virtual void					execute( t_battlefield& battlefield ) const;
		virtual t_combat_cursor_mode	get_action() const;
		virtual t_mouse_window*			get_cursor( t_battlefield&	battlefield, 
													std::string&	help_text );
	protected:
		t_combat_creature*	m_target;
		t_map_point_2d		m_target_point;
	};

	// --------------------------------------------------------
	// melee attack
	// --------------------------------------------------------
	class t_combat_user_action_melee : public t_combat_user_action
	{
	public:
		t_combat_user_action_melee( t_screen_point const&		screen_point,
									t_attack_angle const&		attack_angle,
									t_abstract_combat_object*	target_object );

		virtual void					execute( t_battlefield& battlefield ) const;
		virtual t_combat_cursor_mode	get_action() const;
		virtual t_mouse_window*			get_cursor( t_battlefield&	battlefield, 
													std::string&	help_text );
	protected:
		t_attack_angle				m_attack_angle;
		t_attackable_object*		m_target;
		t_abstract_combat_object*	m_target_object;
	};

	// --------------------------------------------------------
	// move to a point
	// --------------------------------------------------------
	class t_combat_user_action_move : public t_combat_user_action
	{
	public:
		t_combat_user_action_move( t_screen_point const&	screen_point,
								   t_map_point_2d const&	destination );
								
		virtual void					execute( t_battlefield& battlefield ) const;
		virtual t_combat_cursor_mode	get_action() const;
		virtual t_mouse_window*			get_cursor( t_battlefield&	battlefield, 
													std::string&	help_text );
	protected:
		t_map_point_2d				m_destination;
	};

	// --------------------------------------------------------
	// do nothing
	// --------------------------------------------------------
	class t_combat_user_action_none : public t_combat_user_action
	{
	public:
		t_combat_user_action_none(  t_battlefield&			battlefield, 
									t_screen_point const&	screen_point,
									t_combat_creature*		target );

		virtual void					execute( t_battlefield& battlefield ) const;
		virtual t_combat_cursor_mode	get_action() const;
		virtual t_mouse_window*			get_cursor( t_battlefield&	battlefield, 
													std::string&	help_text );
	protected:
		std::string			m_help_text;
		t_combat_creature*	m_target;
	};

	// --------------------------------------------------------
	// ordinary ranged attack
	// --------------------------------------------------------
	class t_combat_user_action_ranged : public t_combat_user_action
	{
	public:
		t_combat_user_action_ranged( t_screen_point const&	screen_point,
									 t_combat_creature*		target );

		virtual void					execute( t_battlefield& battlefield ) const;
		virtual t_combat_cursor_mode	get_action() const;
		virtual t_mouse_window*			get_cursor( t_battlefield&	battlefield, 
													std::string&	help_text );
	protected:
		t_combat_creature*			m_target;
	};

	// --------------------------------------------------------
	// spell action
	// --------------------------------------------------------
	class t_combat_user_action_spell : public t_combat_user_action
	{
	public:
		t_combat_user_action_spell( t_screen_point const& screen_point );

		virtual void					execute( t_battlefield& battlefield ) const;
		virtual t_combat_cursor_mode	get_action() const;
		virtual t_mouse_window*			get_cursor( t_battlefield&	battlefield, 
													std::string&	help_text );
	};
};

// -----------------------------------------------------------------
// structure that holds information about what will happen when
// a left-click occurs.
// -----------------------------------------------------------------
t_combat_user_action::~t_combat_user_action()
{
}

// --------------------------------------------------------
// area effect ranged attack
// --------------------------------------------------------
t_combat_user_action_area::t_combat_user_action_area( t_screen_point const&	screen_point,
													  t_map_point_2d const&	target_point,
													  t_combat_creature*	target )
						 : t_combat_user_action( screen_point )
{
	m_target_point = target_point;
	m_target = target;
}

void t_combat_user_action_area::execute( t_battlefield& battlefield ) const
{
	battlefield.begin_ranged_attack( m_target_point );
}

t_combat_cursor_mode t_combat_user_action_area::get_action() const
{
	return k_combat_mode_ranged;
}

t_mouse_window*	t_combat_user_action_area::get_cursor( t_battlefield& battlefield, 
													   std::string&   help_text )
{
	t_combat_creature*	creature = battlefield.get_acting_creature();
	int					distance = creature->get_edge_distance( m_target_point );
	int					factor = creature->get_range_effect( distance, false );

	battlefield.select_targets( m_target_point, k_ranged_area_attack_radius );

	return battlefield.get_ranged_cursor( m_target, factor, help_text );
}

// --------------------------------------------------------
// melee attack
// --------------------------------------------------------
t_combat_user_action_melee::t_combat_user_action_melee( t_screen_point const&		screen_point,
														t_attack_angle const&		attack_angle,
														t_abstract_combat_object*	target_object )
						  : t_combat_user_action( screen_point )
{
	m_attack_angle = attack_angle;
	m_target_object = target_object;
	m_target = target_object->get_attackable_object();
}

void t_combat_user_action_melee::execute( t_battlefield& battlefield ) const
{
	battlefield.move_to_attack( m_target_object, m_attack_angle.point );
}

t_combat_cursor_mode t_combat_user_action_melee::get_action() const
{
	return k_combat_mode_melee;
}

t_mouse_window*	t_combat_user_action_melee::get_cursor( t_battlefield& battlefield, 
														std::string& help_text )
{
	t_combat_creature*	creature = battlefield.get_acting_creature();
	bool				is_3_headed = creature->has_ability( k_ability_3_headed_attack );
	bool				is_hydra    = creature->has_ability( k_ability_hydra_strike );

	if ( creature->has_ability( k_ability_breath_attack ) )
		battlefield.show_breath_targets( *creature, *m_target, m_screen_point );
	else if (is_3_headed || is_hydra)
		battlefield.show_multi_attack_targets( *creature, *m_target, m_screen_point );
	else
		battlefield.select_target( dynamic_cast<t_combat_creature*>( m_target_object ) );
	return battlefield.get_melee_cursor( m_screen_point, m_target, help_text );
}

// --------------------------------------------------------
// move to a point
// --------------------------------------------------------
t_combat_user_action_move::t_combat_user_action_move( t_screen_point const&	screen_point,
													  t_map_point_2d const&	destination )
						 : t_combat_user_action( screen_point )
{
	m_destination = destination;
}
							
void t_combat_user_action_move::execute( t_battlefield& battlefield ) const
{
	battlefield.begin_move( m_destination );
}

t_combat_cursor_mode t_combat_user_action_move::get_action() const
{
	return k_combat_mode_move;
}

t_mouse_window* t_combat_user_action_move::	get_cursor( t_battlefield& battlefield,
													    std::string& help_text )
{
	battlefield.select_target( 0 );
	return battlefield.get_move_cursor( m_destination, help_text );
}

// --------------------------------------------------------
// do nothing
// --------------------------------------------------------
static t_external_string const k_text_cowardice_singular( "is_cowardly.combat" );
static t_external_string const k_text_cowardice_plural( "are_cowardly.combat" );
static t_external_string const k_text_is_pacifistic( "is_peaceful.combat" );
static t_external_string const k_text_are_pacifistic( "are_peaceful.combat" );

t_combat_user_action_none::t_combat_user_action_none( t_battlefield&		battlefield,
													  t_screen_point const& screen_point,
													  t_combat_creature*	target )
						 : t_combat_user_action( screen_point )
{
	t_combat_creature*	creature = battlefield.get_acting_creature();

	m_target = target;
	battlefield.select_target( target );
	if (target == 0)
		return;

	m_help_text = target->get_name();
	if (!creature->is_friendly( *target ))
	{
		if (creature->is_active( k_spell_song_of_peace ))
		{
			if (creature->get_number() == 1)
				m_help_text = k_text_is_pacifistic;
			else
				m_help_text = k_text_are_pacifistic;
		}
		if (creature->check_cowardice( *target ))
		{
			if (creature->get_number() == 1)
				m_help_text = k_text_cowardice_singular;
			else
				m_help_text = k_text_cowardice_plural;
		}
		m_help_text = replace_keywords( m_help_text, 
										"%the_creature",  creature->get_name( true ),
										"%the_creatures", creature->get_name( true ));
		m_help_text = replace_keywords( m_help_text, "%the_enemy", target->get_name( true ));
	}
}

void t_combat_user_action_none::execute( t_battlefield& battlefield ) const
{
}

t_combat_cursor_mode t_combat_user_action_none::get_action() const
{
	return k_combat_mode_none;
}

t_mouse_window*	t_combat_user_action_none::get_cursor( t_battlefield& battlefield, 
													   std::string& help_text ) 
{
	help_text = m_help_text;

	if (m_target != 0)
		return battlefield.get_info_cursor();
	return battlefield.get_normal_cursor();
};

// --------------------------------------------------------
// ordinary ranged attack
// --------------------------------------------------------
t_combat_user_action_ranged::t_combat_user_action_ranged( t_screen_point const&	screen_point,
														  t_combat_creature*	target )
						   : t_combat_user_action( screen_point )
{
	m_target = target;
}

void t_combat_user_action_ranged::execute( t_battlefield& battlefield ) const
{
	battlefield.begin_ranged_attack( m_target );
}

t_combat_cursor_mode t_combat_user_action_ranged::get_action() const
{
	return k_combat_mode_ranged;
}

t_mouse_window* t_combat_user_action_ranged::get_cursor( t_battlefield& battlefield,
														 std::string& help_text ) 
{
	t_combat_creature*	creature = battlefield.get_acting_creature();
	int					factor = battlefield.get_range_factor( *creature, *m_target );

	battlefield.select_target( m_target );

	return battlefield.get_ranged_cursor( m_target, factor, help_text );
}

// --------------------------------------------------------
// spell action
// --------------------------------------------------------
t_combat_user_action_spell::t_combat_user_action_spell( t_screen_point const& screen_point )
						  : t_combat_user_action( screen_point )
{
}

void t_combat_user_action_spell::execute( t_battlefield& battlefield ) const
{
	battlefield.cast_default_spell( m_screen_point );
}

t_combat_cursor_mode t_combat_user_action_spell::get_action() const
{
	return k_combat_mode_spell;
}

t_mouse_window* t_combat_user_action_spell::get_cursor( t_battlefield& battlefield, 
														std::string& help_text )
{
	return battlefield.get_default_spell()->mouse_move( m_screen_point, help_text );
};

// --------------------------------------------------------
// compare two combat actions and return the one we should retain
// --------------------------------------------------------
t_combat_user_action_ptr compare_user_actions( t_combat_user_action* old_action,
											   t_combat_user_action* new_action )
{
	if (old_action == 0)
		return new_action;

	if ( old_action->get_action() == new_action->get_action() )
		return new_action;

	if (old_action->get_action() == k_combat_mode_none)
		return new_action;

	if (old_action->get_action() == k_combat_mode_move)
		return new_action;

	t_screen_point	delta;
	int				distance;

	delta = old_action->get_screen_point() - new_action->get_screen_point();
	distance = delta.x * delta.x + delta.y * delta.y;
	if (distance >= 25)
		return new_action;

	return old_action;
}

// --------------------------------------------------------
// create a melee action
// --------------------------------------------------------
static t_combat_user_action_ptr create_melee_action( t_battlefield&		    battlefield,
													 t_screen_point const&  screen_point,
													 t_combat_creature*		enemy )
{
	t_combat_creature*	creature = battlefield.get_acting_creature();
	t_attack_angle		attack_angle;

	if (enemy != 0)
	{
		if (!creature->check_cowardice( *enemy ) 
			&& !creature->is_active( k_spell_song_of_peace )
			&& !creature->is_friendly( *enemy )
			&& battlefield.get_attack_angle( screen_point, enemy, attack_angle ))
		{
			return new t_combat_user_action_melee( screen_point, attack_angle, enemy );
		}
	}
	else 
	{
		t_abstract_combat_object* object = battlefield.attackable_hit_test( screen_point );

		if (object == 0 )
			return 0;

		t_attackable_object* target;

		target = object->get_attackable_object();
		if (creature->is_friendly(*target))
			return 0;

		if (!battlefield.get_attack_angle( screen_point, target, attack_angle ))
			return 0;
		return new t_combat_user_action_melee( screen_point, attack_angle, object );
	}
	return 0;
}

// --------------------------------------------------------
// create a move action
// --------------------------------------------------------
static t_combat_user_action_ptr create_move_action( t_battlefield& battlefield,
												 t_screen_point const& screen_point )
{
	t_map_point_2d map_point;

	if (!battlefield.cell_hit_test( screen_point, map_point ))
		return 0;

	t_combat_creature*			creature = battlefield.get_acting_creature();
	t_combat_path_finder&		path_finder = creature->get_path_finder();
	t_combat_path_data const*	path_point = &path_finder.get_data( map_point );
		
	if (!path_point->reachable)
		return 0;

	map_point = path_point->nearest_point;
	path_point = &path_finder.get_data( map_point );
	if (path_point->move_cost == 0)
		return 0;
	
	return new t_combat_user_action_move( screen_point, map_point );
}

// --------------------------------------------------------
// create a ranged action
// --------------------------------------------------------
static t_combat_user_action_ptr create_ranged_action( t_battlefield&		battlefield,
													  t_screen_point const& screen_point,
													  t_combat_creature*	target )
{
	t_combat_creature*	creature = battlefield.get_acting_creature();

	if (!battlefield.can_shoot( *creature ))
		return 0;
	if ( creature->is_active( k_spell_song_of_peace ) )
		return 0;

	if (creature->has_ability( k_ability_area_effect ))
	{
		t_map_point_2d		map_point;

		if (!battlefield.cell_hit_test( screen_point, map_point ))
			return 0;

		int range_factor = battlefield.get_area_range_factor( target, map_point,
															  k_ranged_area_attack_radius );

		if (range_factor == 0)
			return 0;
		return new t_combat_user_action_area( screen_point, map_point, target );
	}
	if (target == 0)
		return 0;

	if (creature->check_cowardice( *target )
		|| creature->is_friendly( *target ))
		return 0;

	int range_factor = battlefield.get_range_factor( *creature, *target );
	
	if (range_factor > 0 )
		return new t_combat_user_action_ranged( screen_point, target );
	return 0;
}

// --------------------------------------------------------
// create a spell action
// --------------------------------------------------------
static t_combat_user_action_ptr create_spell_action( t_battlefield&			battlefield,
													 t_screen_point const&	screen_point )
{
	t_combat_spell* spell = battlefield.get_default_spell();

	if (spell == 0)
		return 0;

	if (!spell->can_cast( screen_point ))
		return 0;

	return new t_combat_user_action_spell( screen_point );
}

// --------------------------------------------------------
// determine correct action at a point
// --------------------------------------------------------
t_combat_user_action_ptr get_combat_user_action( t_battlefield&			battlefield, 
												 t_screen_point const&	screen_point,
												 t_combat_creature*		target )
{
	t_combat_cursor_mode		preferred_action = battlefield.get_preferred_action();
	t_combat_user_action_ptr	result;

	switch (preferred_action)
	{
		case k_combat_mode_melee:
		case k_combat_mode_strike_and_return:
			result = create_melee_action( battlefield, screen_point, target );
			break;

		case k_combat_mode_move:
			result = create_move_action( battlefield, screen_point );
			break;

		case k_combat_mode_ranged:
			result = create_ranged_action( battlefield, screen_point, target );
			break;

		case k_combat_mode_spell:
			result = create_spell_action( battlefield, screen_point );
			break;
	}
	if (result != 0)
		return result;

	result = create_ranged_action( battlefield, screen_point, target );
	if (result != 0)
		return result;

	result = create_melee_action( battlefield, screen_point, target );
	if (result != 0)
		return result;

	result = create_spell_action( battlefield, screen_point );
	if (result != 0)
		return result;

	result = create_move_action( battlefield, screen_point );
	if (result != 0)
		return result;

	return new t_combat_user_action_none( battlefield, screen_point, target );
}


